#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <errno.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <termios.h>
#include <stdlib.h>
#include <sys/epoll.h>

#define MAX_EVENTS 10

#if 0

User Process
User Process  ----> TTY driver ----> line discipline(行规层,负责数据的解析) ----> Uart driver ----> UART
User Process

struct termio {
    unsigned short  c_iflag;        /* 输入模式标志 */   
    unsigned short  c_oflag;        /* 输出模式标志 */   
    unsigned short  c_cflag;        /* 控制模式标志*/    
    unsigned short  c_lflag;        /* local mode flags */ 
    unsigned char  c_line;          /* line discipline 行规层,负责数据的解析和系统的shell或者uart交互数据 */      
    unsigned char  c_cc[NCC];       /* control characters */
};

| tcgetattr   | get terminal attributes，获得终端的属性     |
| ----------- | ----------------------------------------- |
| tcsetattr   | set terminal attributes，修改终端参数     |
| tcflush     | 清空终端未完成的输入/输出请求及数据         |
| cfsetispeed | sets the input baud rate，设置输入波特率   |
| cfsetospeed | sets the output baud rate，设置输出波特率  |
| cfsetspeed  | 同时设置输入、输出波特率                   |
#endif

#define TTY_NAME "/dev/ttymx5"

int open_port(char *name)
{
    int fd;
    // fd = open(name, O_RDWR | O_NOCTTY | O_NDELAY);
    fd = open(name, O_RDWR | O_NOCTTY);
    if (-1 == fd){
        return(-1);
    }

    if (fcntl(fd, F_SETFL, 0) < 0) /* 设置串口为阻塞状态*/
    {
        printf("fcntl failed!\n");
        return -1;
    }

    return fd;
}

int set_opt(int fd, int n_speed, int n_bits, char n_event, int n_stop)
{
    /*保存测试现有串口参数设置，在这里如果串口号等出错，会有相关的出错信息*/
    struct termios newtio, oldtio; 

    if (tcgetattr(fd, &oldtio) != 0) { 
        perror("tcgetattr failed");
        return -1;
    }

    memset(&newtio, 0, sizeof(newtio));

    /* 设置字符大小 */
    newtio.c_cflag |= CLOCAL | CREAD; 
    newtio.c_cflag &= ~CSIZE; 

    /* 访问串口设备的输入和输出时,需要用RAW模式,不通过 line discipline */
    newtio.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);  /*Input*/
    newtio.c_oflag &= ~OPOST;   /*Output*/

    /* 设置数据位 */
    switch (n_bits) {
        case 7:
            newtio.c_cflag |= CS7;
        break;
        case 8:
            newtio.c_cflag |= CS8;
        break;
        default:
            perror("set n_bits failed");
        break;
    }

    /*设置奇偶校验位*/
    switch ( n_event ) {
        case 'O': //奇数
        newtio.c_cflag |= PARENB;
        newtio.c_cflag |= PARODD;
        newtio.c_iflag |= (INPCK | ISTRIP);
        break;
        case 'E': //偶数
        newtio.c_iflag |= (INPCK | ISTRIP);
        newtio.c_cflag |= PARENB;
        newtio.c_cflag &= ~PARODD;
        break;
        case 'N':  //无奇偶校验位
        newtio.c_cflag &= ~PARENB;
        break;
    }

    /*设置波特率*/
    switch ( n_speed ) {
        case 2400:
            cfsetispeed(&newtio, B2400);
            cfsetospeed(&newtio, B2400);
        break;
        case 4800:
            cfsetispeed(&newtio, B4800);
            cfsetospeed(&newtio, B4800);
        break;
        case 9600:
            cfsetispeed(&newtio, B9600);
            cfsetospeed(&newtio, B9600);
        break;
        case 115200:
            cfsetispeed(&newtio, B115200);
            cfsetospeed(&newtio, B115200);
        break;
        case 460800:
            cfsetispeed(&newtio, B460800);
            cfsetospeed(&newtio, B460800);
        break;
        default:
            cfsetispeed(&newtio, B9600);
            cfsetospeed(&newtio, B9600);
        break;
    }

    /*设置停止位*/
    if ( n_stop == 1 ) {
        newtio.c_cflag &=  ~CSTOPB;
    } else if ( n_stop == 2 )
        newtio.c_cflag |=  CSTOPB;

        /*设置等待时间和最小接收字符*/
        newtio.c_cc[VTIME]  = 1; /* 读数据时的最小字节数, 0-->立即返回, 睡眠阻塞进行, n读到n个字节时返回 */
        newtio.c_cc[VMIN] = 0;   /* 等待第1个数据超时时间 单位(10s)*/
                                 /** 0--->一直等待, n--->等待10*n秒
                                  * VMIN设为10表示至少读到10个数据才返回
                                  * 没有数据总不能一直等,可以设置VTIME(单位是10秒)
                                  * 假设VTIME=1,表示
                                  * 10秒内一个数据都没有的话就返回
                                  * 如果10秒内至少读到了1个字节,那就继续等待,
                                  * 完全读到VMIN个数据再返回
                                 */

        /*处理未接收字符*/
        tcflush(fd, TCIFLUSH);
    
    /*激活新配置*/
    if((tcsetattr(fd, TCSANOW, &newtio)) != 0 )
    {
        perror("tty set error");
        return -1;
    }

    return 0;
}

/* 向串口传数据 */
int tty_send_data(int fd, char *send_buf) 
{
    int ret = 0;
    ret = write(fd, send_buf, strlen(send_buf));
    if (ret < 0)
    {
        printf ("write tty data error\n");
        return -1;
    }
    return 1;
}

int tty_read_datas(int fd, char *recv_buf, int len)
{
    struct epoll_event ev, events[MAX_EVENTS];
    int nfds = 0, epollfd = 0, n = 0;

    char val = 0;
    int ret = 0;

    epollfd = epoll_create1(0);
    if (epollfd == -1) {
        perror("epoll_create1");
        return -1;
    }

    ev.events = EPOLLIN; /* 监听输入事件 */
    ev.data.fd = fd;
    if (epoll_ctl(epollfd, EPOLL_CTL_ADD, fd, &ev) == -1) {
        perror("epoll_ctl: listen_sock");
        return -1;
    }

    while(1) {
        nfds = epoll_wait(epollfd, events, MAX_EVENTS, 500); // 500ms, -1表示阻塞
        if (nfds == -1) {
            /* 小于0时, 可以获取 */
            perror("epoll_wait");
            return -1;
        } else if (nfds == 0) {
            /* 驱动中返回0 */
            printf("timeout\n");
        } else {
            for (n = 0; n < nfds; ++n) {
                if (events[n].data.fd == fd) {
                    switch (events[n].events) {
                        case (EPOLLIN):
                            ret = read(fd, recv_buf, len);
                            if (ret < 0) {
                                perror("tty read failed\n");
                                close(events[n].data.fd);
                                epoll_ctl(epollfd, EPOLL_CTL_DEL, events[n].data.fd, &ev);
                                return -1;
                            }
                            printf("Get Data: %s\n", recv_buf);
                            memset(recv_buf, 0, sizeof(recv_buf));
                        break;
                        default:
                            printf("Unknow events\n");
                        break;
                    }
                }
            }
        }
    }

    return 0;
}

int main(int argc, char const *argv[])
{
    int fd = 0;
    int ret = 0;
    char recv_buf[512];

    fd = open_port(TTY_NAME);
    if (fd < 0) {
        printf("open %s err!\n", TTY_NAME);
        return -1; 
    }

    ret = set_opt(fd, 115200, 8, 'N', 1);
    if (ret < 0) {
        printf("set port err!\n");
        close(fd);
		return -1;
    }

    ret = tty_read_datas(fd, recv_buf, sizeof(recv_buf));
    if (ret < 0) {
        close(fd);
        return -1;
    }

    close(fd);
    
    return 0;
}
